<?php
/** Framework_Controller_Exception */
require_once 'Framework/Controller/Exception.php';
/** Framework_Controller_Plugin_Broker */
require_once 'Framework/Controller/Plugin/Broker.php';
/**
 * Его назначение состоит в инициализации окружения запроса, проложении маршрута приходящего
 * запроса и последующем запуске выявленных действий. Он агрегирует все ответы и возвращает
 * их по завершении процесса.
 * 
 * @category   Framework
 * @package    Framework_Controller
 */
class Framework_Controller_Front
{
    /**
     * Директория с пользовательскими контроллерами
     *
     * @var string
     */
    protected $_controllerDir = null;
    /**
     * Базовый url
     *
     * @var string
     */
    protected $_baseUrl = null;
    /**
     * Instance of Framework_Controller_Request_Abstract
     * @var Framework_Controller_Request_Abstract
     */
    protected $_request = null;
    /**
     * Instance of Zend_Controller_Response_Abstract
     * @var Zend_Controller_Response_Abstract
     */
    protected $_response = null;
    /**
     * Instance of Zend_Controller_Router_Interface
     * @var Zend_Controller_Router_Interface
     */
    protected $_router = null;
    /**
     * Instance of Zend_Controller_Plugin_Broker
     * @var Zend_Controller_Plugin_Broker
     */
    protected $_plugins = null;
    /**
     * Whether or not exceptions encountered in {@link dispatch()} should be
     * thrown or trapped in the response object
     * @var boolean
     */
    protected $_throwExceptions = false;
    /**
     * Constructor
     *
     * Instantiate using {@link getInstance()}; front controller is a singleton
     * object.
     *
     * Instantiates the plugin broker.
     *
     * @return void
     */
    private function __construct()
    {
        $this->_plugins = new Framework_Controller_Plugin_Broker();
    }
    /**
     * Instance of Zend_Controller_Dispatcher_Interface
     * @var Zend_Controller_Dispatcher_Interface
     */
    protected $_dispatcher = null;
    /**
     * Framework_Controller_Front object
     *
     * @var Framework_Controller_Front
     */
    protected static $_instance = null;
    /**
     * Возвращает инстанцинированный объект Framework_Controller_Front
     *
     * @return Controller_Front
     */
    public static function getInstance()
    {
        if (null === self::$_instance) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }
    /**
     * Установка базового url
     *
     * @return Controller_Front
     */
    public function setBaseUrl($url)
    {
        if (is_string($url)) {
            $this->_baseUrl = rtrim((string) $url, '/\\');
        } else {
            throw new Exception('Базовый url должен быть строкой');
        }
        return $this;
    }
    /**
     * Установка директории с контроллерами
     *
     * @return Controller_Front
     */
    public function setControllerDirectory($directory)
    {
        $this->getDispatcher()->setControllerDirectory($directory);
        return $this;
    }
    
    public function getControllerDirectory()
    {
        return $this->getDispatcher()->getControllerDirectory();
    }
    
    /**
     * Return the dispatcher object.
     *
     * @return Framework_Controller_Dispatcher_Interface
     */
    public function getDispatcher()
    {
        /**
         * Instantiate the default dispatcher if one was not set.
         */
        if (! $this->_dispatcher instanceof Framework_Controller_Dispatcher_Interface) {
            require_once 'Framework/Controller/Dispatcher/Standard.php';
            $this->_dispatcher = new Framework_Controller_Dispatcher_Standard();
        }
        return $this->_dispatcher;
    }
    
    /**
     * Set request class/object
     *
     * Set the request object.  The request holds the request environment.
     *
     * If a class name is provided, it will instantiate it
     *
     * @param string|Zend_Controller_Request_Abstract $request
     * @throws Zend_Controller_Exception if invalid request class
     * @return Zend_Controller_Front
     */
    public function setRequest($request)
    {
        if (! $request instanceof Framework_Controller_Request_Abstract) {
            throw new Framework_Controller_Exception('Invalid request class');
        }
        $this->_request = $request;
        return $this;
    }
    
    public function getRequest()
    {
        return $this->_request;
    }
    
    /**
     * Set response class/object
     *
     * Set the response object.  The response is a container for action
     * responses and headers. Usage is optional.
     *
     * If a class name is provided, instantiates a response object.
     *
     * @param string|Zend_Controller_Response_Abstract $response
     * @throws Zend_Controller_Exception if invalid response class
     * @return Zend_Controller_Front
     */
    public function setResponse($response)
    {
        if (! $response instanceof Framework_Controller_Response_Abstract) {
            throw new Framework_Controller_Exception('Invalid response class');
        }
        $this->_response = $response;
        return $this;
    }
    
    /**
     * Return the response object.
     *
     * @return null|Zend_Controller_Response_Abstract
     */
    public function getResponse()
    {
        return $this->_response;
    }
    
    /**
     * Set router class/object
     *
     * Set the router object.  The router is responsible for mapping
     * the request to a controller and action.
     *
     * If a class name is provided, instantiates router with any parameters
     * registered via {@link setParam()} or {@link setParams()}.
     *
     * @param string|Zend_Controller_Router_Interface $router
     * @throws Zend_Controller_Exception if invalid router class
     * @return Zend_Controller_Front
     */
    public function setRouter($router)
    {
        $this->_router = $router;
        return $this;
    }
    /**
     * Return the router object.
     *
     * Instantiates a Zend_Controller_Router_Rewrite object if no router currently set.
     *
     * @return Zend_Controller_Router_Interface
     */
    public function getRouter()
    {
        if (null == $this->_router) {
            require_once 'Framework/Controller/Router/Rewrite.php';
            $this->setRouter(new Framework_Controller_Router_Rewrite());
        }
        return $this->_router;
    }
    /**
     * Register a plugin.
     *
     * @param  Zend_Controller_Plugin_Abstract $plugin
     * @param  int $stackIndex Optional; stack index for plugin
     * @return Zend_Controller_Front
     */
    public function registerPlugin(Framework_Controller_Plugin_Abstract $plugin)
    {
        $this->_plugins->registerPlugin($plugin);
        return $this;
    }
    /**
     * Set the throwExceptions flag and retrieve current status
     *
     * Set whether exceptions encounted in the dispatch loop should be thrown
     * or caught and trapped in the response object.
     *
     * Default behaviour is to trap them in the response object; call this
     * method to have them thrown.
     *
     * Passing no value will return the current value of the flag; passing a 
     * boolean true or false value will set the flag and return the current 
     * object instance.
     *
     * @param boolean $flag Defaults to null (return flag state)
     * @return boolean|Zend_Controller_Front Used as a setter, returns object; as a getter, returns boolean
     */
    public function throwExceptions ($flag = null)
    {
        if ($flag !== null) {
            $this->_throwExceptions = (bool) $flag;
            return $this;
        }
        return $this->_throwExceptions;
    }
    /**
     * Диспетчер
     * 
     * Основное действо происходит здесь. Нужно дорабатывать.
     *
     * @return void
     */
    public function dispatch (Framework_Controller_Request_Abstract $request = null, Framework_Controller_Response_Abstract $response = null)
    {
        /**
         * Инстанцирование объекта request (HTTP version)
         */
        if (null !== $request) {
            $this->setRequest($request);
        } elseif ((null === $request) && (null === ($request = $this->getRequest()))) {
            require_once 'Framework/Controller/Request/Http.php';
            $request = new Framework_Controller_Request_Http();
            $this->setRequest($request);
        }
        /**
         * Set base URL of request object, if available
         */
        if (is_callable(array($this->_request , 'setBaseUrl'))) {
            if (null !== $this->_baseUrl) {
                $this->_request->setBaseUrl($this->_baseUrl);
            }
        }
        /**
         * Инстанцирование объекта response (HTTP version)
         */
        if (null !== $response) {
            $this->setResponse($response);
        } elseif ((null === $this->_response) && (null === ($this->_response = $this->getResponse()))) {
            require_once 'Framework/Controller/Response/Http.php';
            $response = new Framework_Controller_Response_Http();
            $this->setResponse($response);
        }
        /**
         * Register request and response objects with plugin broker
         */
        $this->_plugins->setRequest($this->_request)->setResponse($this->_response);
        /**
         * Инициализация роутера
         */
        $router = $this->getRouter();
        /**
         * Инициализация диспетчера
         */
        $dispatcher = $this->getDispatcher();
        
        // Begin dispatch
        try {
            /**
             * Notify plugins of router startup
             */
            $this->_plugins->routeStartup($this->_request);
            
            $router->route($this->_request);
            
            /**
             * Notify plugins of router completion
             */
            $this->_plugins->routeShutdown($this->_request);
            
            $dispatcher->dispatch($this->_request, $this->_response);
            
        } catch (Exception $e) {
            if (! $this->throwExceptions()) {
                throw $e;
            }
            //TODO Проверить что здесь происходит
            $this->_response->setException($e);
        }
        $this->getResponse()->sendResponse();
    }
}